﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;
using STSdb.Data;

namespace STSdb.MTL
{
    /// <summary>
    /// Answers for safe access to the schema elements
    /// for now is born in Transaction Coordinator, not in STSdbEngine, that user wouldn't be able to access it.
    /// </summary>
    public class SchemaCoordinator
    {
        TransactionsCoordinator _transactionsCoordinator = null;
        ///// <summary>
        ///// All schema mutation operations must come via this lock
        ///// </summary>
        //ReaderWriterLockSlim _sync_schema = new ReaderWriterLockSlim();

        object schema_lock = new object();

        /// <summary>
        /// Class for managing STSdb Tables compacting procedure
        /// </summary>
        public StsTablesCompactor StsTablesCompacter = null;

        /// <summary>
        /// Holds MTL variant of STSdb Tables schemes
        /// </summary>
        public StsTablesSchema StsTablesSchema = null;

        public SchemaCoordinator(TransactionsCoordinator transactionsCoordinator)
        {            
            this._transactionsCoordinator = transactionsCoordinator;

            this.StsTablesCompacter = new StsTablesCompactor(this._transactionsCoordinator);
            this.StsTablesSchema = new StsTablesSchema(this._transactionsCoordinator);

            this.Init_SystemTables();
        }


        #region "SYSTEM TABLES INITIALIZATION"
       

        private void Init_SystemTables()
        {
            //Reserving and initalizing RawFiles for different STSdb.MTL system needs.
            this.Init_SystemTablesInRawFiles();

            //Registering a user's-tables extra schema layer.
            this.StsTablesSchema.Init();

            //Creating "Exhaling" system tables
            //this.CreateSysTable_ExhaledTables_Names();

        }

        #region "System Tables In Raw Files"

        public const string @sys_SystemTablesInRawFiles_stringName = "@sys_SystemTablesInRawFiles_Names";
        /// <summary>
        /// String System Table Reciding in RawFile name, and RawFIle handle
        /// </summary>
        XTable<string, ulong> @sys_SystemTablesInRawFiles_References = null;

        private void Init_SystemTablesInRawFiles()
        {
            /*
                @sys_SystemTablesInRawFiles_Names will hold as a Key internal MTL entity name and as a value RawFileHandle. where entity data is stored
            */  

            try
            {
                //creating or opening @sys_SystemTablesInRawFiles_Names
                Locator l = new Locator(@sys_SystemTablesInRawFiles_stringName);

                if (this._transactionsCoordinator.StorageEngine.Scheme.Exists(l))
                {
                    @sys_SystemTablesInRawFiles_References = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable<string, ulong>(new Locator(@sys_SystemTablesInRawFiles_stringName));
                }
                else
                {
                    @sys_SystemTablesInRawFiles_References = this._transactionsCoordinator.StorageEngine.Scheme.CreateXTable<string, ulong>(new Locator(@sys_SystemTablesInRawFiles_stringName));
                    this._transactionsCoordinator.StorageEngine.Scheme.Commit();
                }

                //Filling or Reading @sys_SystemTablesInRawFiles_Names rawFiles Handles

                this.ReserveRawFilesForSystemTables();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
            
        
        private void ReserveRawFilesForSystemTables()
        {
            /* STSdbTablesCompacter*/

            //Reserving rawFile for holding On-Line Compacting information
            ulong OnLineCompacter_RawFileHandle = 0;
            if (!@sys_SystemTablesInRawFiles_References.TryGet("STSdbTablesCompacter", out OnLineCompacter_RawFileHandle))
            {

                this.StsTablesCompacter.CompacterRawFileReference = this._transactionsCoordinator.StorageEngine.FileSystem.CreateFile();
                @sys_SystemTablesInRawFiles_References["STSdbTablesCompacter"] = this.StsTablesCompacter.CompacterRawFileReference.Handle;
                @sys_SystemTablesInRawFiles_References.Commit();  
                //Every such commit must change locator VIA LOCK, which we don't have for this table, cause it's created and filled in the beginning,
                //But we have the same table in StsTableSchema, there for all commits, new locator is re-read for @sys_sts_tables_schemes
            }
            else
            {
                this.StsTablesCompacter.CompacterRawFileReference = this._transactionsCoordinator.StorageEngine.FileSystem.OpenFile(OnLineCompacter_RawFileHandle);
            }
            
            /////////////////////////////////////////////////////////////
        }


        #endregion
        


        #endregion

        #region "Schema Table Information"
        public class SchemaInfo
        {
            public SchemaInfo()
            {
                Size = 0;
                SystemSize = 0;
                RawFileLength = 0;
            }

            public ulong Size { get; set; }
            public ulong SystemSize { get; set; }
            public ulong RawFileLength { get; set; }

            public override string ToString()
            {
                return String.Format("STSdb.Schema.Info: Size: {0}; SystemSize: {1}; RawFileSize: {2};", Size, SystemSize, RawFileLength);
            }
        }

        public SchemaInfo GetSchemaInfo()
        {
            SchemaInfo si=new SchemaInfo();

            lock (schema_lock)
            {
                si.Size = this._transactionsCoordinator.StorageEngine.Scheme.Size;
                si.SystemSize = this._transactionsCoordinator.StorageEngine.Scheme.SystemSize;
                si.RawFileLength = this._transactionsCoordinator.StorageEngine.Scheme.RawFile.Length;
            }

            return si;
        }
        #endregion

        /// <summary>
        /// Retrieves XTable from the storage, using new locator. If table doesn't exist, creates new XTable, commits schema and returns table.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T1"></typeparam>
        /// <param name="tableName"></param>
        /// <returns></returns>
        public XTable<T,T1> OpenTable<T, T1>(string tableName)
        {
            XTable<T, T1> ret = null;



            //Getting real table name
            string wrappedTableName = this.StsTablesSchema.GetUsersTableName(tableName);
            

            try
            {
                lock (schema_lock)
                {
                    lock (this._transactionsCoordinator._SYNC_COMMIT)
                        ret = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable<T, T1>(new Locator(wrappedTableName));

                    if (ret != null)
                        return ret;

                    lock (this._transactionsCoordinator._SYNC_COMMIT)
                    {
                        ret = this._transactionsCoordinator.StorageEngine.Scheme.CreateXTable<T, T1>(new Locator(wrappedTableName));
                        this._transactionsCoordinator.StorageEngine.Scheme.Commit();
                    }

                    return ret;
                }

                


            }
            catch (Exception ex)
            {
                
                throw ex;
            }


            #region "old"

            //_sync_schema.EnterUpgradeableReadLock();
            //try
            //{
                
            //    //Getting real table name
            //    string wrappedTableName = this.StsTablesSchema.GetUsersTableName(tableName);


            //    //if (wrappedTableName == String.Empty)
            //    //{
            //    //    throw new Exception("STSdb.MTL.SchemaCoordinator.OpenTable: " + tableName + " can't be open by the user");                   
            //    //}

            //    lock (this._transactionsCoordinator._SYNC_COMMIT)
            //    {
            //        ret = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable<T, T1>(new Locator(wrappedTableName));
            //    }

            //    if (ret != null)
            //        return ret;

            //    _sync_schema.EnterWriteLock();
            //    try
            //    {
            //        lock (this._transactionsCoordinator._SYNC_COMMIT)
            //        {
            //            ret = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable<T, T1>(new Locator(wrappedTableName));

            //            if (ret != null)
            //                return ret;

            //            /* IF NOT all has to be stored in one rawFile */
            //            //if to store in different raw files, unremark this (take away all locks for MainRawFileLock in transaction.cs) and remark next 3 lines

            //            ret = this._transactionsCoordinator.StorageEngine.Scheme.CreateXTable<T, T1>(new Locator(wrappedTableName));
            //            this._transactionsCoordinator.StorageEngine.Scheme.Commit();



            //            /* IF all has to be stored in one rawFile */
            //            ////Testing to create all tables in one RawFile, all insert, delete, update operations have to go via MainRawFileLock
            //            //ret = CreateTableInCommonRawFile<T, T1>();
            //            //this._transactionsCoordinator.StorageEngine.Scheme.Register<T, T1>(new Locator(tableName), ret);
            //            //this._transactionsCoordinator.StorageEngine.Scheme.Commit();
            //            /********************************************/
            //        }

            //        return ret;
            //    }
            //    catch (System.Exception ex)
            //    {
            //        throw ex;
            //        //this._transactionsCoordinator.StopEngineInFailureCase("STSdb.MTL.SchemaCoordinator.OpenTable. " + tableName + "  " + ex.ToString());
            //        //return null;
            //    }
            //    finally
            //    {
            //        _sync_schema.ExitWriteLock();
            //    }
            //}
            //catch (System.Exception ex)
            //{
            //    throw ex;
            //    //this._transactionsCoordinator.StopEngineInFailureCase("SchemaCoordinatorException. " + ex.ToString());
            //    //return null;
            //}
            //finally
            //{
            //    _sync_schema.ExitUpgradeableReadLock();
            //}
            #endregion

        }

        #region "Technical Open, CreateOrOpen, Delete, synchronized with _sync_schema lock"

        /// <summary>
        /// Opens XTable via _sync_schema lock exactly by specified table name, without converting to exhaled name and return reference to it.
        /// If table not found returns NULL
        /// </summary>
        /// <param name="tableName"></param>
        /// <returns></returns>
        public XTable OpenXTableTechical(string tableName)
        {
            //Can only "open" XTable and returns references to it

            try
            {
                Locator l = new Locator(tableName);
                XTable xtable = null;

                lock (schema_lock)
                {
                    if (this._transactionsCoordinator.StorageEngine.Scheme.Exists(l))
                    {
                        lock (this._transactionsCoordinator._SYNC_COMMIT)
                        {
                            xtable = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable(l);
                        }
                    }

                    return xtable;
                }

                

            }
            catch (Exception ex)
            {
                throw ex;
            }

         
        }

        /// <summary>
        /// Creates or opens table via _sync_schema NonGenerics
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="keyType"></param>
        /// <param name="recordType"></param>
        /// <returns></returns>
        public XTable CreateOrOpenXTableTechical(string tableName, Type keyType, Type recordType)
        {
            try
            {
                Locator l = new Locator(tableName);
                XTable xtable = null;

                lock (schema_lock)
                {
                    if (this._transactionsCoordinator.StorageEngine.Scheme.Exists(l))
                    {
                        lock (this._transactionsCoordinator._SYNC_COMMIT)
                        {
                            xtable = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable(l);
                        }
                    }
                    else
                    {
                        lock (this._transactionsCoordinator._SYNC_COMMIT)
                        {
                            xtable = this._transactionsCoordinator.StorageEngine.Scheme.CreateXTable(l, keyType, recordType);
                            this._transactionsCoordinator.StorageEngine.Scheme.Commit();
                        }
                    }

                    return xtable;
                }



            }
            catch (Exception ex)
            {
                throw ex;
            }
                      
        }

        /// <summary>
        /// Deletes table from the schema and from the VFS, uses exactly supplied tableName.
        /// Makes nothing with StsTablesSchema
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="deleteRawFile"></param>
        public void DeleteXTableTechnical(string tableName, bool deleteRawFile)
        {

            try
            {
                Locator l = new Locator(tableName);
                XTable table = null;

                lock (schema_lock)
                {
                    if (this._transactionsCoordinator.StorageEngine.Scheme.Exists(l))
                    {
                        table = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable(l);

                        this._transactionsCoordinator.StorageEngine.Scheme.Delete(l);
                        this._transactionsCoordinator.StorageEngine.Scheme.Commit();


                        if (deleteRawFile)
                        {
                            table.RawFile.Close();
                            this._transactionsCoordinator.StorageEngine.FileSystem.DeleteFile(table.RawFile.Handle);
                        }
                    }
                }



            }
            catch (Exception ex)
            {
                throw ex;
            }

           
        }

        #endregion


        #region "Storing all table in one Raw File. Till no way to garbage collect raw file. This method has no sense"

        //Commit also must go via one lock

        //STSdb.Files.RawFile mainRawFile = null;
        ///// <summary>
        ///// Lock for raw file used for Inserts, Updates and deletes.
        ///// </summary>
        //public object MainRawFileLock = new object();

        ///// <summary>
        ///// Creates a table in a common raw File
        ///// </summary>
        ///// <typeparam name="T"></typeparam>
        ///// <typeparam name="T1"></typeparam>
        ///// <returns></returns>
        //private XTable<T,T1> CreateTableInCommonRawFile<T, T1>()
        //{
        //    if (mainRawFile == null)
        //    {
        //        //trying to open @sys_startTable to get rawFile, if not found create it
        //        XTable<byte, byte> startTable = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable<byte, byte>(new Locator("@sys_startTable"));
        //        if (startTable == null)
        //        {
        //            //creating startTable
        //            lock (MainRawFileLock)
        //            {
        //                mainRawFile = this._transactionsCoordinator.StorageEngine.FileSystem.CreateFile();
        //                startTable = new XTable<byte, byte>(mainRawFile, mainRawFile.Length);

        //                this._transactionsCoordinator.StorageEngine.Scheme.Register<byte, byte>(new Locator("@sys_startTable"), startTable);
        //                this._transactionsCoordinator.StorageEngine.Scheme.Commit();
        //            }
        //        }
        //        else
        //        {
        //            //using start table as MainRawFile
        //            mainRawFile = startTable.RawFile;
        //        }
        //    }

        //    XTable<T, T1> ret = null;

        //    lock (MainRawFileLock)
        //    {
        //        ret = new XTable<T, T1>(mainRawFile, mainRawFile.Length);
        //    }

        //    return ret;
        //}

        #endregion


        /// <summary>
        /// Deletes table from Schema and TablesCoordinator 
        /// !!! DOESN'T DELETE RAW FILE. Possily will be obsolete in the future or will be second parameter added deleteAlsoRawFile
        /// </summary>
        /// <param name="tableName"></param>
        public void DeleteTable(string tableName)
        {
            try
            {
                string wrappedTableName = this.StsTablesSchema.GetUsersTableName(tableName);

                //Deleting file from StsTablesSchema(just making active file 0 - original name)
                this.StsTablesSchema.DeleteTable(tableName);

                var locator = new Locator(wrappedTableName);

                lock (schema_lock)
                {
                    if (!this._transactionsCoordinator.StorageEngine.Scheme.Exists(locator))
                        return;


                    this._transactionsCoordinator.StorageEngine.Scheme.Delete(locator);
                    this._transactionsCoordinator.StorageEngine.Scheme.Commit();
                }

                //Deleting table from Table Coordinator
                this._transactionsCoordinator.TablesCoordinator.DeleteTable(tableName);

            }
            catch (Exception ex)
            {
                throw ex;
            }
                   

        }


        /// <summary>
        /// !!!!!!!!!!!!!!!!!!!! Check what can happen with open locators - one test was done, seems to be all ok. Just empty data in the output
        /// Temp function helps to delete table together with raw file
        /// </summary>
        /// <param name="tableName"></param>
        public void DeleteTableWithRawFile(string tableName, bool deleteAlsoRawFile)
        {

            try
            {
                string wrappedTableName = this.StsTablesSchema.GetUsersTableName(tableName);

                //Deleting file from StsTablesSchema(just making active file 0 - original name)
                this.StsTablesSchema.DeleteTable(tableName);

                var locator = new Locator(wrappedTableName);
                XTable table = null;

                lock (schema_lock)
                {
                    if (!this._transactionsCoordinator.StorageEngine.Scheme.Exists(locator))
                        return;

                    lock (this._transactionsCoordinator._SYNC_COMMIT)
                    {
                        table = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable(locator);
                    }

                    this._transactionsCoordinator.StorageEngine.Scheme.Delete(locator);
                    this._transactionsCoordinator.StorageEngine.Scheme.Commit();
                }

                //Deleting table from Table Coordinator
                this._transactionsCoordinator.TablesCoordinator.DeleteTable(tableName);

                if (deleteAlsoRawFile)
                {
                    //closing Raw File
                    table.RawFile.Close();
                    //Deleting file from file system so it can be garbage colected
                    this._transactionsCoordinator.StorageEngine.FileSystem.DeleteFile(table.RawFile.Handle);
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }

          

        }









        /// <summary>
        /// Returns TRUE if table exists inside of the engine
        /// </summary>
        /// <param name="tableName"></param>
        /// <returns></returns>
        public bool IF_TableExists(string tableName)
        {
            

            try
            {
                string wrappedName = this.StsTablesSchema.GetUsersTableName(tableName);

                lock (schema_lock)
                {
                    return this._transactionsCoordinator.StorageEngine.Scheme.Exists(new Locator(wrappedName));
                }
            }
            catch (Exception ex)
            {                
                throw ex;
            }
                      
        }



        /// <summary>
        /// Creates table snapshot
        /// </summary>
        /// <param name="tableName"></param>
        public void RegisterSnapshotTable<T,T1>(Locator locator, XTable<T,T1> tableSnapshot)
        {
            //_sync_schema.EnterWriteLock();
            //try
            //{
                

            //    //committing snapshotted table
            //    lock (this._transactionsCoordinator._SYNC_COMMIT)
            //    {
            //        this._transactionsCoordinator.StorageEngine.Scheme.Register(locator, tableSnapshot);
            //        //committing schema
            //        this._transactionsCoordinator.StorageEngine.Scheme.Commit();

            //        tableSnapshot.Commit();
            //    }
            //}
            //catch (System.Exception ex)
            //{
            //    throw new Exception("STSdb.MTL: RegisterSnapshotTable failed " + locator.Items[0]);
            //}
            //finally
            //{
            //    _sync_schema.ExitWriteLock();
            //}

        }

        /// <summary>
        /// Technical function. Low level table from snapshot restoration
        /// </summary>
        /// <param name="tableNameForRestore"></param>
        /// <param name="snapshotName"></param>
        public void RestoreTableFromSnapshot(string tableNameForRestore, string snapshotName)
        {
            //_sync_schema.EnterWriteLock();
            //try
            //{
            //    Locator snapshotLocator = new Locator(snapshotName);


            //    //committing snapshotted table
            //    lock (this._transactionsCoordinator._SYNC_COMMIT)
            //    {
            //        if (!this._transactionsCoordinator.StorageEngine.Scheme.Exists(snapshotLocator))
            //            return;


            //        Locator tableForRestore = new Locator(tableNameForRestore);
            //        XTable snapshotTable = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable(snapshotLocator);

            //        this._transactionsCoordinator.StorageEngine.Scheme.Register(tableForRestore, snapshotTable);
            //        //committing schema
            //        this._transactionsCoordinator.StorageEngine.Scheme.Commit();

            //        snapshotTable.Commit();
            //    }
            //}
            //catch (System.Exception ex)
            //{
            //    throw new Exception("STSdb.MTL: RestoreTableFromSnapshot failed ");
            //}
            //finally
            //{
            //    _sync_schema.ExitWriteLock();
            //}

        }
        

        /// <summary>
        /// Returns list of all tables names in the database.
        /// </summary>
        /// <returns></returns>
        public List<string> GetAllTablesNames()
        {
            try
            {
                List<string> l = new List<string>();

                lock (schema_lock)
                {
                    
                    //l = this._transactionsCoordinator.StorageEngine.Scheme.Forward().Select(r => this.StsTablesSchema.GetUsersTableName(r.Key.Items[0])).ToList();
                    l = this._transactionsCoordinator.StorageEngine.Scheme.Forward().Select(r => this.StsTablesSchema.GetOriginalUserTableNameFromSchema(r.Key.Items[0])).ToList();
                }

                return l;
            }
            catch (Exception ex)
            {
                throw ex;
            }
                       
        }
            

        /// <summary>
        /// Temp solution. Not for usage
        /// Returns first element Table Key Type, second element value type.
        /// if list is empty - table not found, can grow up, change it onto class.
        /// </summary>
        /// <param name="tableName"></param>
        /// <returns></returns>
        public List<string> GetTableDefinition(string tableName)
        {
            List<string> ret = new List<string>();

            lock (schema_lock)
            {
                var locator = new Locator(tableName);
                if (this._transactionsCoordinator.StorageEngine.Scheme.Exists(locator))
                {
                    var table = this._transactionsCoordinator.StorageEngine.Scheme.OpenXTable(locator);
                    ret.Add(table.KeyType.ToString());
                    ret.Add(table.RecordType.ToString());
                    table.Close();
                }               
            }

            return ret;
        }

    }
}
